package com.hualan.utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 这是数据库的工具类, 主要是提供数据库的一些操作方法
 * 例如: 通用的增删改查  通用的查询
 *
 * @author 28993
 */
public class C3P0Util {
    //在加载类的时候,就把 数据连接池创建好
    private static DataSource ds = new ComboPooledDataSource();

    //提供一个私有构造方法, 目的是不想让别人new对象.
    private C3P0Util() {
    }

    /**
     * 获取一个连接 !!注意!! 用到事务的时候,才需要使用此方法, 如果不用事务,无需使用此方法
     * 获取的连接需要关闭(放回数据库连接池中)
     *
     * @return
     */
    public static Connection getConnection() {
        Connection con = null;
        try {
            con = ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }

    /**
     * 关闭一个连接 !!注意!! 用到事务的时候,才需要使用此方法, 如果不用事务,无需使用此方法
     * 本方法用于关闭  本类中 Connection getConnection() 返回的连接对象
     *
     * @param con
     */
    public static void close(Connection con) {
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 通用的增, 删, 改方法
     *
     * @param sql        增删改sql语句
     * @param parameters sql语句中?的值
     * @return 返回sql语句执行之后, 影响的行数
     */
    public static int update(String sql, Object... parameters) {
        int row = 0;
        try (Connection con = ds.getConnection();
             PreparedStatement stmt = con.prepareStatement(sql)) {
            //给sql语句中的?赋值
            for (int i = 0; i < parameters.length; i++) {
                stmt.setObject(i + 1, parameters[i]);
            }
            row = stmt.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return row;
    }


    /**
     * 事务版--通用的增, 删, 改方法
     *
     * @param sql        增删改sql语句
     * @param con        连接对象
     * @param parameters sql语句中?的值
     * @return 返回sql语句执行之后, 影响的行数
     */
    public static int update(String sql, Connection con, Object... parameters) {
        int row = 0;
        try (PreparedStatement stmt = con.prepareStatement(sql)) {
            //给sql语句中的?赋值
            for (int i = 0; i < parameters.length; i++) {
                stmt.setObject(i + 1, parameters[i]);
            }
            row = stmt.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return row;
    }

    /**
     * 通用查询方法
     *
     * @param sql              查询的sql语句
     * @param clazz            希望封装成什么类型 例如希望封装成 Student类型,参数写成 Student.class
     * @param underlineToCamel 是否需要把表中字段带_的转换成小驼峰名称 true表示转, false表示不转
     * @param parameters       sql语句中?的值
     * @return 返回一个List, list是clazz对应类型的对象
     */
    public static <T> List<T> query(String sql, Class<T> clazz, boolean underlineToCamel, Object... parameters) {
        List<T> list = new ArrayList<>();
        try (Connection con = ds.getConnection();
             PreparedStatement stmt = con.prepareStatement(sql);) {
            //给sql语句中的?赋值
            for (int i = 0; i < parameters.length; i++) {
                stmt.setObject(i + 1, parameters[i]);
            }
            //获取结果集
            ResultSet resultSet = stmt.executeQuery();

            //结果集的元数据
            ResultSetMetaData rsmd = resultSet.getMetaData();
            //获取查询的列数
            int columnCount = rsmd.getColumnCount();
            //创建一个String数组,存放查询的所有列的列名
            String[] columnLabelArr = new String[columnCount];
            for (int i = 1; i <= columnCount; i++) {
                //获取列的标签名(列名)
                String columnLabel = rsmd.getColumnLabel(i);
                columnLabelArr[i - 1] = columnLabel;
            }
            //遍历查询结果
            while (resultSet.next()) {
                //通过反射创建对象!
                Constructor<T> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);//跳过访问权限检查
                T obj = constructor.newInstance();

                for (int i = 0; i < columnLabelArr.length; i++) {
                    //通过下标获取列名
                    String columnLabel = columnLabelArr[i];
                    //列对应的值
                    Object value = resultSet.getObject(columnLabel);

                    //通过反射获取类中的Field
                    if (underlineToCamel) {
                        //下划线转小驼峰
                        columnLabel = underlineToCamel(columnLabel);
                    }
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);//跳过访问权限的检查
                    //通过反射给属性赋值
                    if (value != null) {
                        field.set(obj, value);
                    }
                }
                list.add(obj);
            }
            resultSet.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 事务版--通用查询方法
     *
     * @param sql              查询的sql语句
     * @param con              连接对象
     * @param clazz            希望封装成什么类型 例如希望封装成 Student类型,参数写成 Student.class
     * @param underlineToCamel 是否需要把表中字段带_的转换成小驼峰名称 true表示转, false表示不转
     * @param parameters       sql语句中?的值
     * @return 返回一个List, list是clazz对应类型的对象
     */
    public static <T> List<T> query(String sql, Connection con, Class<T> clazz, boolean underlineToCamel, Object... parameters) {
        List<T> list = new ArrayList<>();
        try (PreparedStatement stmt = con.prepareStatement(sql);) {
            //给sql语句中的?赋值
            for (int i = 0; i < parameters.length; i++) {
                stmt.setObject(i + 1, parameters[i]);
            }
            //获取结果集
            ResultSet resultSet = stmt.executeQuery();

            //结果集的元数据
            ResultSetMetaData rsmd = resultSet.getMetaData();
            //获取查询的列数
            int columnCount = rsmd.getColumnCount();
            //创建一个String数组,存放查询的所有列的列名
            String[] columnLabelArr = new String[columnCount];
            for (int i = 1; i <= columnCount; i++) {
                //获取列的标签名(列名)
                String columnLabel = rsmd.getColumnLabel(i);
                columnLabelArr[i - 1] = columnLabel;
            }
            //遍历查询结果
            while (resultSet.next()) {
                //通过反射创建对象!
                Constructor<T> constructor = clazz.getDeclaredConstructor();
                constructor.setAccessible(true);//跳过访问权限检查
                T obj = constructor.newInstance();

                for (int i = 0; i < columnLabelArr.length; i++) {
                    //通过下标获取列名
                    String columnLabel = columnLabelArr[i];
                    //列对应的值
                    Object value = resultSet.getObject(columnLabel);

                    //通过反射获取类中的Field
                    if (underlineToCamel) {
                        //下划线转小驼峰
                        columnLabel = underlineToCamel(columnLabel);
                    }
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);//跳过访问权限的检查
                    //通过反射给属性赋值
                    if (value != null) {
                        field.set(obj, value);
                    }
                }
                list.add(obj);
            }
            resultSet.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }


    /**
     * 通用查询方法
     *
     * @param sql        查询的sql语句
     * @param clazz      希望封装成什么类型 例如希望封装成 Student类型,参数写成 Student.class
     * @param parameters sql语句中?的值
     * @return 返回一个List, list是clazz对应类型的对象
     */
    public static <T> List<T> query(String sql, Class<T> clazz, Object... parameters) {
        return query(sql, clazz, true, parameters);
    }

    /**
     * 事务版---通用查询方法
     *
     * @param sql        查询的sql语句
     * @param con        连接对象
     * @param clazz      希望封装成什么类型 例如希望封装成 Student类型,参数写成 Student.class
     * @param parameters sql语句中?的值
     * @return 返回一个List, list是clazz对应类型的对象
     */
    public static <T> List<T> query(String sql, Connection con, Class<T> clazz, Object... parameters) {
        return query(sql, con, clazz, true, parameters);
    }

    /**
     * 查询单个值的通用查询
     *
     * @param sql              查询的sql语句
     * @param clazz            希望封装成的类型
     * @param underlineToCamel 是否需要把表中字段带_的转换成小驼峰名称 true表示转, false表示不转
     * @param parameters       sql语句中?的值
     * @return 如果没有查到返回null, 如果查到了返回第一个结果
     */
    public static <T> T queryOne(String sql, Class<T> clazz, boolean underlineToCamel, Object... parameters) {
        List<T> list = query(sql, clazz, underlineToCamel, parameters);
        return list.size() == 0 ? null : list.get(0);
    }

    /**
     * 事务版---查询单个值的通用查询
     *
     * @param sql              查询的sql语句
     * @param con              连接对象
     * @param clazz            希望封装成的类型
     * @param underlineToCamel 是否需要把表中字段带_的转换成小驼峰名称 true表示转, false表示不转
     * @param parameters       sql语句中?的值
     * @return 如果没有查到返回null, 如果查到了返回第一个结果
     */
    public static <T> T queryOne(String sql, Connection con, Class<T> clazz, boolean underlineToCamel, Object... parameters) {
        List<T> list = query(sql, con, clazz, underlineToCamel, parameters);
        return list.size() == 0 ? null : list.get(0);
    }

    /**
     * 查询单个值的通用查询
     *
     * @param sql        查询的sql语句
     * @param clazz      希望封装成的类型
     * @param parameters sql语句中?的值
     * @return 如果没有查到返回null, 如果查到了返回第一个结果
     */
    public static <T> T queryOne(String sql, Class<T> clazz, Object... parameters) {
        List<T> list = query(sql, clazz, true, parameters);
        return list.size() == 0 ? null : list.get(0);
    }

    /**
     * 事务版---查询单个值的通用查询
     *
     * @param sql        查询的sql语句
     * @param con        连接对象
     * @param clazz      希望封装成的类型
     * @param parameters sql语句中?的值
     * @return 如果没有查到返回null, 如果查到了返回第一个结果
     */
    public static <T> T queryOne(String sql, Connection con, Class<T> clazz, Object... parameters) {
        List<T> list = query(sql, con, clazz, true, parameters);
        return list.size() == 0 ? null : list.get(0);
    }

    /**
     * 把下划线命名转换为小驼峰法命名
     *
     * @param underline
     * @return
     */
    private static String underlineToCamel(String underline) {
        //创建一个可变字符串.
        StringBuilder sb = new StringBuilder(underline);
        //遍历字符串中的每个字符
        for (int i = 0; i < sb.length(); i++) {
            char c = sb.charAt(i);
            if (c == '_' && i != sb.length() - 1) {
                //如果是下划线,找到它下个字符
                char nextChar = underline.charAt(i + 1);
                //把这个字符变成大写
                nextChar = Character.toUpperCase(nextChar);
                //替换字符
                sb.setCharAt(i + 1, nextChar);
            }
        }
        String str = sb.toString();

        return str.replace("_", "");
    }

    /**
     * 通用查询方法
     *
     * @param sql        查询的sql语句
     * @param parameters sql语句中?的值
     * @return 返回一个List, list的元素是Map类型
     */
    public static List<Map<String, Object>> query(String sql, Object... parameters) {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        try (Connection con = ds.getConnection();
             PreparedStatement stmt = con.prepareStatement(sql);) {
            //给sql语句中的?赋值
            for (int i = 0; i < parameters.length; i++) {
                stmt.setObject(i + 1, parameters[i]);
            }
            //执行查询,获取结果集
            ResultSet resultSet = stmt.executeQuery();
            //获取结果集元数据
            ResultSetMetaData rsmd = resultSet.getMetaData();
            //获取查询的列数
            int columnCount = rsmd.getColumnCount();
            //创建一个String数组,存放列名
            String[] columnNames = new String[columnCount];
            for (int i = 1; i <= columnCount; i++) {
                //通过列号 获取列名
                String columnLabel = rsmd.getColumnLabel(i);
                columnNames[i - 1] = columnLabel;
            }
            //遍历结果集
            while (resultSet.next()) {
                Map<String, Object> map = new HashMap<String, Object>();
                //遍历每一列
                for (int i = 0; i < columnNames.length; i++) {
                    //获取列名
                    String columnName = columnNames[i];
                    //通过列名获取值
                    Object value = resultSet.getObject(columnName);
                    //把列名和值存入map中
                    map.put(columnName, value);
                }
                list.add(map);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 事务版---通用查询方法
     *
     * @param sql        查询的sql语句
     * @param con        连接对象
     * @param parameters sql语句中?的值
     * @return 返回一个List, list的元素是Map类型
     */
    public static List<Map<String, Object>> query(String sql, Connection con, Object... parameters) {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        try (PreparedStatement stmt = con.prepareStatement(sql);) {
            //给sql语句中的?赋值
            for (int i = 0; i < parameters.length; i++) {
                stmt.setObject(i + 1, parameters[i]);
            }
            //执行查询,获取结果集
            ResultSet resultSet = stmt.executeQuery();
            //获取结果集元数据
            ResultSetMetaData rsmd = resultSet.getMetaData();
            //获取查询的列数
            int columnCount = rsmd.getColumnCount();
            //创建一个String数组,存放列名
            String[] columnNames = new String[columnCount];
            for (int i = 1; i <= columnCount; i++) {
                //通过列号 获取列名
                String columnLabel = rsmd.getColumnLabel(i);
                columnNames[i - 1] = columnLabel;
            }
            //遍历结果集
            while (resultSet.next()) {
                Map<String, Object> map = new HashMap<String, Object>();
                //遍历每一列
                for (int i = 0; i < columnNames.length; i++) {
                    //获取列名
                    String columnName = columnNames[i];
                    //通过列名获取值
                    Object value = resultSet.getObject(columnName);
                    //把列名和值存入map中
                    map.put(columnName, value);
                }
                list.add(map);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 通用查询方法
     *
     * @param sql        查询的sql语句
     * @param parameters sql语句中?的值
     * @return 返回一个Map类型
     */
    public static Map<String, Object> queryOne(String sql, Object... parameters) {
        List<Map<String, Object>> list = query(sql, parameters);
        return list.size() == 0 ? null : list.get(0);
    }

    /**
     * 事务版---通用查询方法
     *
     * @param sql        查询的sql语句
     * @param con        连接对象
     * @param parameters sql语句中?的值
     * @return 返回一个Map类型
     */
    public static Map<String, Object> queryOne(String sql, Connection con, Object... parameters) {
        List<Map<String, Object>> list = query(sql, con, parameters);
        return list.size() == 0 ? null : list.get(0);
    }

}
